package ai.people.platform.archives.service.impl;

import ai.people.core.nebula.aspect.NebulaAutoRelease;
import ai.people.core.nebula.entity.NebulaGraph;
import ai.people.core.nebula.factory.NebulaSessionFactoryBean;
import ai.people.core.nebula.util.NebulaQueryUtils;
import ai.people.core.nebula.util.VidGenerateUtils;
import ai.people.netmon.framework.businessenum.GraphBizEnum;
import ai.people.netmon.framework.constant.GraphConstant;
import ai.people.netmon.framework.constant.GraphDirection;
import ai.people.netmon.framework.domain.archives.entity.ArchivesBaseInfo;
import ai.people.netmon.framework.domain.archives.entity.GraphVid;
import ai.people.netmon.framework.domain.archives.request.ArchivesAddAllRequest;
import ai.people.netmon.framework.domain.archives.request.ArchivesUpdateAllRequest;
import ai.people.netmon.framework.domain.archives.vo.*;
import ai.people.netmon.framework.domain.graph.EdgeBase;
import ai.people.netmon.framework.domain.graph.FrontGraphBase;
import ai.people.netmon.framework.domain.graph.edges.BlankEdge;
import ai.people.netmon.framework.domain.graph.enums.GraphEdgeEnum;
import ai.people.netmon.framework.domain.graph.enums.GraphTagEnum;
import ai.people.netmon.framework.domain.graph.tags.PersonArchivesTag;
import ai.people.netmon.framework.exception.enums.CommonEnum;
import ai.people.netmon.framework.exception.enums.GraphExceptionEnum;
import ai.people.netmon.framework.exception.type.BusinessException;
import ai.people.netmon.framework.utils.AssertUtils;
import ai.people.netmon.utils.NumeralPool;
import ai.people.netmon.utils.StringPool;
import ai.people.netmon.utils.UUIDUtils;
import ai.people.platform.archives.service.*;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.vesoft.nebula.client.graph.exception.IOErrorException;
import com.vesoft.nebula.client.graph.net.SessionWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.function.Function;

import static java.util.stream.Collectors.*;

/**
 * @author zhenglin
 * @date 2022/4/8
 */
@Service
@Slf4j
@RequiredArgsConstructor
@NebulaAutoRelease
public class GraphServiceImpl implements GraphService {

    @Autowired
    ArchivesService archivesService;
    @Autowired
    ArchivesBaseInfoService archivesBaseInfoService;
    @Autowired
    GraphAdminService graphAdminService;
    @Autowired
    PersonArchivesTagsService personArchivesTagsService;
    @Autowired
    PersonArchivesEdgesService personArchivesEdgesService;
    @Autowired
    GraphVidService graphVidService;

    /**
     * nebula会话工厂bean
     */
    private final NebulaSessionFactoryBean nebulaSessionFactoryBean;

    /**
     * 标签编码映射名称
     */
    private Map<String, String> tagCodeMapName;
    /**
     * 边编码映射名称
     */
    private Map<String, String> edgeCodeMapName;
    /**
     * 边缘代码映射类型
     */
    private Map<String, String> edgeCodeMapType;

    /**
     * 边缘映射
     * todo 优化
     */
    @Value("#{${archives.edgeMapping}}")
    private Map<String, String> edgeMapping;

    /**
     * 初始化 加载标签映射 和 边编码映射
     */
    private void init() {
        //todo 后续改为存在redis
        List<ArchivesTagsVO> archivesTags = personArchivesTagsService.listTags();
        tagCodeMapName = archivesTags.stream().collect(groupingBy(ArchivesTagsVO::getTagCode, collectingAndThen(toList(), x -> x.get(0).getTagName())));

        List<ArchivesEdgesVO> archivesEdges = personArchivesEdgesService.listEdges();
        edgeCodeMapName = archivesEdges.stream().collect(toMap(ArchivesEdgesVO::getEdgeCode, ArchivesEdgesVO::getEdgeName));
        edgeCodeMapType = archivesEdges.stream().collect(toMap(ArchivesEdgesVO::getEdgeCode, x -> x.getType().toString()));
    }

    private Map<String, String> getTagCodeMapName() {
        if (tagCodeMapName == null) {
            init();
        }
        return tagCodeMapName;
    }

    private Map<String, String> getEdgeCodeMapName() {
        if (edgeCodeMapName == null) {
            init();
        }
        return edgeCodeMapName;
    }

    private Map<String, String> getEdgeCodeMapType() {
        if (edgeCodeMapType == null) {
            init();
        }
        return edgeCodeMapType;
    }


    /**
     * 获取节点信息
     *
     * @param vid 从视频
     * @return {@link ArchivesGraph.Node}
     * @throws IOErrorException             ioerror异常那么
     * @throws UnsupportedEncodingException 不支持编码异常
     */
    @Override
    public ArchivesGraph.Node getNodeVo(String vid) throws Exception {
        NebulaGraph.NebulaNode nebulaNode = NebulaQueryUtils.getVertexByVid(Objects.requireNonNull(nebulaSessionFactoryBean.getObject()), vid);
        if (nebulaNode == null) {
            return null;
        }
        ArchivesGraph.Node node = buildNodeVo(null, nebulaNode, null, true);
        node.getLabelCfg().setNodesType(null);
        return node;
    }

    /**
     * 档案路径
     *
     * @param id               id
     * @param hideArchivesInfo 隐藏档案信息
     * @return {@link ArchivesGraph}
     * @throws Exception 异常
     */
    @Override
    public ArchivesGraph archivesPath(Long id, boolean hideArchivesInfo) throws Exception {
        //查询图的坐标
        ArchivesBaseInfo baseInfo = archivesBaseInfoService.getOne(new QueryWrapper<ArchivesBaseInfo>().lambda().select(ArchivesBaseInfo::getCoordinateAxis).eq(ArchivesBaseInfo::getId, id));

        JSONObject coordinateAxisJson = new JSONObject();
        if (baseInfo != null) {
            String coordinateAxis = baseInfo.getCoordinateAxis();
            if (StringUtils.isNotEmpty(coordinateAxis)) {
                coordinateAxisJson = JSONObject.parseObject(coordinateAxis);
            }
        }
        //档案主体的vid
        String archivesVid = NebulaQueryUtils.buildVid(GraphTagEnum.PERSON_ARCHIVES.vidPrefix, id);
        //查询图根节点关联的ID
        GraphVid graphVid = graphVidService.getOne(new QueryWrapper<GraphVid>().lambda()
                .eq(GraphVid::getRootVid, archivesVid).eq(GraphVid::getBizType, GraphBizEnum.ARCHIVES.biz)
        );
        Set<String> archivesHasVidSet = new HashSet<>();

        if (graphVid != null && StringUtils.isNotBlank(graphVid.getVids())) {
            archivesHasVidSet.addAll(Arrays.asList(graphVid.getVids().split(StringPool.COMMA)));
            archivesHasVidSet.add(archivesVid);
        }

        //查询档案主体的路径
        NebulaGraph path = graphAdminService.path(archivesVid, GraphDirection.BOTH, archivesHasVidSet);

        ArchivesGraph vo = new ArchivesGraph();

        Set<ArchivesGraph.Edge> edges = new HashSet<>();
        Set<ArchivesGraph.Node> nodes = new HashSet<>();

        Set<NebulaGraph.NebulaPath> paths = path.getPaths();
        //根据路径构造边和节点
        for (NebulaGraph.NebulaPath nebulaPath : paths) {
            NebulaGraph.NebulaNode edge = nebulaPath.getEdge();
            NebulaGraph.NebulaNode from = nebulaPath.getFrom();
            String fromVid = (String) from.getVid();
            NebulaGraph.NebulaNode to = nebulaPath.getTo();
            String toVid = (String) to.getVid();
            //构造起始节点
            ArchivesGraph.Node fromNode = buildNodeVo(archivesVid, from, coordinateAxisJson, hideArchivesInfo);
            nodes.add(fromNode);
            //构造结束节点
            ArchivesGraph.Node toNode = buildNodeVo(archivesVid, to, coordinateAxisJson, hideArchivesInfo);
            nodes.add(toNode);
            //构造边
            ArchivesGraph.Edge edgeVo = buildEdgeVo(edge, fromVid, toVid, false, true);
            //重写hashcode以判重
            if (edges.contains(edgeVo)) {
                edges.remove(edgeVo);
                edgeVo = buildEdgeVo(edge, fromVid, toVid, true, true);
            }
            edges.add(edgeVo);
        }
        if (CollectionUtils.isEmpty(nodes)) {
            NebulaGraph.NebulaNode rootNode = path.getRootNode();
            FrontGraphBase.Node node = buildNodeVo(archivesVid, rootNode, coordinateAxisJson, false);
            nodes.add(node);
        }
        vo.setNodes(nodes);
        vo.setEdges(edges);
        vo.setArchivesId(id.toString());
        return vo;
    }

    /**
     * 记录坐标
     *
     * @param coordinateAxis 坐标轴
     * @param vid            从视频
     * @param node           节点
     */
    private void putToCoordinateAxis(JSONObject coordinateAxis, String vid, ArchivesGraph.Node node) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("x", node.getX());
        jsonObject.put("y", node.getY());
        coordinateAxis.put(vid, jsonObject);
    }

    /**
     * 保存边
     *
     * @param session
     * @param edge      边缘
     * @param sourceVid 源视频
     * @param targetVid 目标从视频
     * @throws IOErrorException             ioerror异常那么
     * @throws UnsupportedEncodingException 不支持编码异常
     */
    private void saveEdge(SessionWrapper session, ArchivesGraph.Edge edge, String sourceVid, String targetVid) throws IOErrorException, UnsupportedEncodingException {
        ArchivesGraph.Edge.Style style = edge.getStyle();
        ArchivesGraph.Edge.LabelCfg labelCfg = edge.getLabelCfg();
        String type = labelCfg.getType();
        String edgeCode = labelCfg.getEdgeCode().getCode();
        Object edgeValue = null;
        //如果是空白边
        if (StringUtils.isBlank(edgeCode)) {
            edgeCode = GraphEdgeEnum.BLANK.edgeName;
            edgeValue = new BlankEdge(type);
        }
        boolean startArrow = style.getStartArrow() != null && style.getStartArrow();
        boolean endArrow = style.getEndArrow() != null && style.getEndArrow();
        //插入边的关系
        //sourceNode <- targetNode
        if (startArrow) {
            boolean saveEdge = NebulaQueryUtils.insertEdge(session, targetVid, sourceVid, edgeCode, edgeValue);
            AssertUtils.isTrue(saveEdge, CommonEnum.FAIL);
        }
        //sourceNode -> targetNode
        if (endArrow) {
            boolean saveEdge = NebulaQueryUtils.insertEdge(session, sourceVid, targetVid, edgeCode, edgeValue);
            AssertUtils.isTrue(saveEdge, CommonEnum.FAIL);
        }
        //如果是无向的关系则直接使用source和target
        if (!startArrow && !endArrow) {
            EdgeBase edgeBase = new EdgeBase();
            edgeBase.setUndirected(0);
            boolean saveEdge = NebulaQueryUtils.insertEdge(session, sourceVid, targetVid, edgeCode, edgeBase);
            AssertUtils.isTrue(saveEdge, CommonEnum.FAIL);
        }
    }

    /**
     * 保存节点
     *
     * @param session
     * @param rootVid          根vid
     * @param groupByVid       通过视频
     * @param source           源
     * @param coordinateAxis   坐标轴
     * @param alreadySaveNodes 已经保存节点
     * @return {@link String}
     * @throws IOErrorException             ioerror异常那么
     * @throws UnsupportedEncodingException 不支持编码异常
     */
    private String saveNode(SessionWrapper session, String rootVid, Map<String, ArchivesGraph.Node> groupByVid, Object source, JSONObject coordinateAxis, Map<String, String> alreadySaveNodes) throws IOErrorException, UnsupportedEncodingException {
        ArchivesGraph.Node node = groupByVid.get(source);
        String nodeId = node.getId();
        if (alreadySaveNodes.containsKey(nodeId)) {
            return alreadySaveNodes.get(nodeId);
        }
        //构造vid
        String vid = buildVidByNodeVO(node, rootVid);
        alreadySaveNodes.put(nodeId, vid);
        //保存节点
        boolean upsertSourceVertex = convertAndInsert(session, vid, node);
        AssertUtils.isTrue(upsertSourceVertex, CommonEnum.FAIL);
        putToCoordinateAxis(coordinateAxis, vid, node);
        return vid;
    }


    /**
     * 转换和插入
     *
     * @param session
     * @param vid      从视频
     * @param rootNode 根节点
     * @return boolean
     * @throws IOErrorException             ioerror异常那么
     * @throws UnsupportedEncodingException 不支持编码异常
     */
    private boolean convertAndInsert(SessionWrapper session, String vid, ArchivesGraph.Node rootNode) throws IOErrorException, UnsupportedEncodingException {
        ArchivesGraph.Node.LabelCfg labelCfg = rootNode.getLabelCfg();
        ArchivesGraph.Node.Value tagCode = labelCfg.getTagCode();
        String tagCodeStr = tagCode.getCode();
        //档案的另外处理
        if (GraphTagEnum.PERSON_ARCHIVES.tagName.equals(tagCodeStr)) {
            //档案主体已经保存，不需要再次保存
            if (Objects.equals(labelCfg.getNodesType(), NumeralPool.ONE)) {
                return true;
            }
            return saveArchives(session, vid, rootNode);
        }
        Map<String, Object> properties = labelCfg.getProperties();

        Class<?> classByTagName = GraphTagEnum.getClassByTagName(tagCodeStr);
        if (classByTagName == null) {
            throw new BusinessException(GraphExceptionEnum.TAG_NOT_MAPPING);
        }

        Object data = JSONObject.parseObject(JSONObject.toJSONString(properties), classByTagName);

        return NebulaQueryUtils.insertVertex(session, vid, tagCodeStr, data);
    }

    /**
     * 保存档案
     *
     * @param session
     * @param vid      从视频
     * @param rootNode 根节点
     * @return boolean
     * @throws IOErrorException             ioerror异常那么
     * @throws UnsupportedEncodingException 不支持编码异常
     */
    private boolean saveArchives(SessionWrapper session, String vid, ArchivesGraph.Node rootNode) throws IOErrorException, UnsupportedEncodingException {
        ArchivesGraph.Node.LabelCfg labelCfg = rootNode.getLabelCfg();
        ArchivesGraph.Node.Value tagCode = labelCfg.getTagCode();
        String tagCodeStr = tagCode.getCode();
        Map<String, Object> properties = labelCfg.getProperties();
        ArchivesVO data = JSONObject.parseObject(JSONObject.toJSONString(properties), ArchivesVO.class);
        PersonArchivesTag tag = new PersonArchivesTag(data);
        return NebulaQueryUtils.insertVertex(session, vid, tagCodeStr, tag);
    }

    /**
     * 构建vid
     *
     * @param node    节点
     * @param rootVid 根vid
     * @return {@link String}
     */
    private String buildVidByNodeVO(ArchivesGraph.Node node, String rootVid) {
        ArchivesGraph.Node.LabelCfg labelCfg = node.getLabelCfg();
        //档案主体直接返回
        if (Objects.equals(labelCfg.getNodesType(), NumeralPool.ONE)) {
            return rootVid;
        }
        String id = node.getId();

        String sourceTagCode = labelCfg.getTagCode().getCode();
        Map<String, Object> propertiesValues = labelCfg.getProperties();
        GraphTagEnum byTagName = GraphTagEnum.getByTagName(sourceTagCode);
        if (byTagName == null) {
            throw new BusinessException(GraphExceptionEnum.TAG_NOT_MAPPING);
        }
        //如果副主体档案
        if (Objects.equals(labelCfg.getNodesType(), NumeralPool.THREE)) {
            if (id.startsWith(GraphTagEnum.PERSON_ARCHIVES.vidPrefix)) {
                return id;
            }
            return NebulaQueryUtils.buildVid(GraphTagEnum.PERSON_ARCHIVES.vidPrefix, UUIDUtils.getShort());
        } else {
            String archivesId = rootVid.replaceAll(GraphTagEnum.PERSON_ARCHIVES.vidPrefix + StringPool.COLON, "");
            List<Object> vidValues = VidGenerateUtils.handleSpecialVid(propertiesValues, byTagName, archivesId);
            return NebulaQueryUtils.buildVid(byTagName.vidPrefix, vidValues);
        }

    }

    /**
     * 处理特殊的vid
     *
     * @param propertiesValues 属性值
     * @param byTagName        通过标签名称
     * @return {@link Object[]}
     */
    private Object[] handleSpecialVid(Map<String, Object> propertiesValues, GraphTagEnum byTagName) {
        //需要多个属性才能生成vid的情况
        int size = byTagName.vidPropertiesKeys().size();
        Object[] vidValues = new Object[size];
        for (int i = 0; i < size; i++) {
            String key = byTagName.vidPropertiesKeys().get(i);
            if (key.startsWith(GraphConstant.MD5)) {
                //如果vid需要加密
                key = key.replaceAll(GraphConstant.MD5, "");
                vidValues[i] = DigestUtils.md5Hex(propertiesValues.get(key).toString());
            } else if (key.startsWith(GraphConstant.BASE)) {
                //如果vid需要加上档案ID
                key = key.replaceAll(GraphConstant.BASE, "");
            } else {
                vidValues[i] = propertiesValues.get(key);
            }
        }
        return vidValues;
    }

    /**
     * 检查图
     */
    private void checkGraph(Set<ArchivesGraph.Node> nodes, Set<ArchivesGraph.Edge> edges) {
        //如果只是档案主体
        if (nodes.size() == NumeralPool.ONE && (edges == null || edges.size() == NumeralPool.ZERO)) {
            for (FrontGraphBase.Node node : nodes) {
                if (Objects.equals(node.getLabelCfg().getNodesType(), NumeralPool.ONE)) {
                    return;
                }
            }
            throw new BusinessException(GraphExceptionEnum.OUTLIER);
        }
        Set<String> edgesNodeVidList = edges.stream().map(x -> {
            List<String> list = new ArrayList<>();
            list.add(x.getSource());
            list.add(x.getTarget());
            return list;
        }).flatMap(Collection::stream).collect(toSet());
        Set<String> nodesVidList = nodes.stream().map(ArchivesGraph.Node::getId).collect(toSet());
        //边中的节点数量 不等于 nodes数量
        if (edgesNodeVidList.size() != nodesVidList.size()) {
            throw new BusinessException(GraphExceptionEnum.PATH_NOT_MAPPING_NODE);
        }
        edgesNodeVidList.removeAll(nodesVidList);
        //如果还有节点说明边中的节点不在nodes中
        if (edgesNodeVidList.size() != NumeralPool.ZERO) {
            throw new BusinessException(GraphExceptionEnum.PATH_NOT_MAPPING_NODE);
        }
        //检查孤点
        checkOutlier(edges);

        //检查node的properties的属性是否为空
        for (ArchivesGraph.Node node : nodes) {
            ArchivesGraph.Node.LabelCfg labelCfg = node.getLabelCfg();
            String code = labelCfg.getTagCode().getCode();
            //获取配置的类上的是否含有非空注解
            Map<String, String> classNotEmptyFieldsByTagName = GraphTagEnum.getClassNotEmptyFieldsByTagName(code);
            if (!classNotEmptyFieldsByTagName.isEmpty()) {
                Map<String, Object> properties = labelCfg.getProperties();
                classNotEmptyFieldsByTagName.forEach((key, value) -> {
                            Object o = properties.get(key);
                            if (o == null || (o instanceof String && StringUtils.isEmpty((String) o))) {
                                throw new BusinessException(value, GraphExceptionEnum.FIELD_NOT_EMPTY.getCode());
                            }
                        }
                );
            }
        }
    }

    /**
     * 检查孤点
     *
     * @param edges 边缘
     */
    private void checkOutlier(Set<ArchivesGraph.Edge> edges) {
        Map<String, List<String>> map = new HashMap<>();
        for (ArchivesGraph.Edge edge : edges) {
            String source = edge.getSource();
            String target = edge.getTarget();

            if (map.containsKey(source)) {
                map.get(source).add(target);
            } else {
                List<String> list = new ArrayList<>();
                list.add(target);
                map.put(source, list);
            }

            if (map.containsKey(target)) {
                map.get(target).add(source);
            } else {
                List<String> list = new ArrayList<>();
                list.add(source);
                map.put(target, list);
            }
        }

        List<String> list = new ArrayList<>(map.keySet());
        String start = list.get(0);

        for (int i = 1; i < list.size(); i++) {
            Set<String> hasVisit = new HashSet<>();
            String n = list.get(i);
            if (!path(map, start, n, hasVisit)) {
                throw new BusinessException(GraphExceptionEnum.OUTLIER);
            }
        }
    }

    /**
     * 递归判断是否能连通
     *
     * @param map      地图
     * @param start    开始
     * @param end      结束
     * @param hasVisit 有访问
     * @return boolean
     */
    private boolean path(Map<String, List<String>> map, String start, String end, Set<String> hasVisit) {
        List<String> list = map.get(start);

        if (hasVisit.contains(start) || list == null) {
            return false;
        }
        if (list.contains(end)) {
            return true;
        }
        hasVisit.add(start);
        for (String s : list) {
            if (path(map, s, end, hasVisit)) {
                return true;
            }
        }
        return false;
    }


    /**
     * 得到边缘映射
     *
     * @return {@link Map}<{@link String}, {@link String}>
     */
    @Override
    public Map<String, String> getEdgeMapping() {
        return edgeMapping;
    }

    /**
     * 检查图
     *
     * @param archivesGraph 档案图
     * @return boolean
     */
    @Override
    public boolean checkGraph(FrontGraphBase archivesGraph) {
        Set<ArchivesGraph.Edge> edges = archivesGraph.getEdges();
        try {
            checkOutlier(edges);
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    /**
     * 保存档案+图
     *
     * @param all 所有
     * @return {@link String}
     * @throws Exception 异常
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String saveAll(ArchivesAddAllRequest all) throws Exception {
        //保存档案信息
        ArchivesAddVO archives = all.getArchives();
        String archivesId = archivesService.addArchives(archives);
        //保存图
        FrontGraphBase graph = all.getGraph();
        saveGraph(graph.getNodes(), graph.getEdges(), archivesId, new ArchivesVO().buildArchivesVO(archives, archivesId));
        return archivesId;
    }

    /**
     * 修改档案+图
     *
     * @param all 所有
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateAll(ArchivesUpdateAllRequest all) throws Exception {
        //更新档案
        ArchivesVO archives = all.getArchives();
        archivesService.updateArchives(archives);
        //更新图
        Long id = archives.getId();
        FrontGraphBase graph = all.getGraph();
        saveGraph(graph.getNodes(), graph.getEdges(), id.toString(), archives);
    }


    /**
     * 保存graph
     *
     * @param nodes      节点
     * @param edges      边缘
     * @param archivesId 档案id
     * @param archives   档案
     * @throws Exception 异常
     */
    private void saveGraph(Set<ArchivesGraph.Node> nodes, Set<ArchivesGraph.Edge> edges, String archivesId, ArchivesVO archives) throws Exception {
        Set<String> nodeIds = new HashSet<>();
        SessionWrapper session = nebulaSessionFactoryBean.getObject();
        //1、校验图
        checkGraph(nodes, edges);
        //构造档案主体的vid
        String rootVid = NebulaQueryUtils.buildVid(GraphTagEnum.PERSON_ARCHIVES.vidPrefix, archivesId);
        //2、删除原有的图
        deleteRoot(session, rootVid);
        //3、先保存档案主体的图
        saveRootArchive(nodes, archives, session, rootVid);
        //4、遍历边保存每一个节点
        JSONObject coordinateAxis = saveEachNode(nodes, edges, session, rootVid, nodeIds);
        //5、更新坐标
        updateCoordinateAxis(archivesId, coordinateAxis);
        //6.保存关系
        saveRootVidRelations(rootVid, nodeIds);
    }

    /**
     * 保存此业务数据根节点 和关联 节点的vid
     *
     * @param rootVid 根vid
     * @param nodeIds 节点id
     */
    private void saveRootVidRelations(String rootVid, Set<String> nodeIds) {
        GraphVid graphVid = new GraphVid();
        graphVid.setRootVid(rootVid);
        graphVid.setBizType(GraphBizEnum.ARCHIVES.biz);
        nodeIds.remove(rootVid);
        graphVid.setVids(StringUtils.join(nodeIds, StringPool.COMMA));
        graphVidService.saveConflict(graphVid);
    }

    /**
     * 更新坐标
     *
     * @param archivesId     档案id
     * @param coordinateAxis 坐标轴
     */
    private void updateCoordinateAxis(String archivesId, JSONObject coordinateAxis) {
        ArchivesBaseInfo archivesBaseInfo = new ArchivesBaseInfo();
        archivesBaseInfo.setId(Long.valueOf(archivesId));
        archivesBaseInfo.setCoordinateAxis(coordinateAxis.toJSONString());
        archivesBaseInfoService.updateCoordinateAxisById(archivesBaseInfo);
    }

    /**
     * 遍历边保存每一个节点
     *
     * @param nodes   节点
     * @param edges   边缘
     * @param session 会话
     * @param rootVid 根vid
     * @param nodeIds
     * @return {@link JSONObject}
     * @throws IOErrorException             ioerror异常那么
     * @throws UnsupportedEncodingException 不支持编码异常
     */
    private JSONObject saveEachNode(Set<ArchivesGraph.Node> nodes, Set<ArchivesGraph.Edge> edges, SessionWrapper session, String rootVid, Set<String> nodeIds) throws IOErrorException, UnsupportedEncodingException {
        //按vid聚合
        Map<String, ArchivesGraph.Node> groupByVid = nodes.stream().collect(toMap(ArchivesGraph.Node::getId, Function.identity()));

        //记录已经保存了的节点
        Map<String, String> alreadySaveNodes = new HashMap<>(nodes.size());
        //记录坐标
        JSONObject coordinateAxis = new JSONObject();
        //4、遍历边保存节点
        for (ArchivesGraph.Edge edge : edges) {
            String sourceVid = saveNode(session, rootVid, groupByVid, edge.getSource(), coordinateAxis, alreadySaveNodes);
            String targetVid = saveNode(session, rootVid, groupByVid, edge.getTarget(), coordinateAxis, alreadySaveNodes);
            saveEdge(session, edge, sourceVid, targetVid);
            nodeIds.add(sourceVid);
            nodeIds.add(targetVid);
        }
        return coordinateAxis;
    }

    /**
     * 删除根
     *
     * @param session 会话
     * @param rootVid 根vid
     * @throws IOErrorException ioerror异常那么
     */
    private void deleteRoot(SessionWrapper session, String rootVid) throws IOErrorException, UnsupportedEncodingException {
//        boolean delete = NebulaQueryUtils.deleteRoot(session, rootVid, 1, 50, GraphEdgeEnum.getEdgeNameList());
        boolean delete = NebulaQueryUtils.deleteByGoRoot(session, rootVid, 1, 50, null);
        AssertUtils.isTrue(delete, CommonEnum.FAIL);
    }

    /**
     * 先保存档案主体的图
     *
     * @param nodes    节点
     * @param archives 档案
     * @param session  会话
     * @param rootVid  根vid
     * @throws IOErrorException             ioerror异常那么
     * @throws UnsupportedEncodingException 不支持编码异常
     */
    private void saveRootArchive(Set<ArchivesGraph.Node> nodes, ArchivesVO archives, SessionWrapper session, String rootVid) throws IOErrorException, UnsupportedEncodingException {
        //档案主体
        ArchivesGraph.Node rootNode = nodes.stream().filter(x -> Objects.equals(x.getLabelCfg().getNodesType(), NumeralPool.ONE)).findFirst()
                .orElseThrow(() -> new BusinessException(CommonEnum.FAIL));
        ArchivesGraph.Node.LabelCfg labelCfg = rootNode.getLabelCfg();
        ArchivesGraph.Node.Value tagCode = labelCfg.getTagCode();
        String tagCodeStr = tagCode.getCode();
        PersonArchivesTag tag = new PersonArchivesTag(archives);
        boolean updateRootNode = NebulaQueryUtils.insertVertex(session, rootVid, tagCodeStr, tag);
        AssertUtils.isTrue(updateRootNode, CommonEnum.FAIL);
    }


    /**
     * 构建node vo
     *
     * @param archivesVid        档案vid
     * @param nebulaNode         星云节点
     * @param coordinateAxisJson 坐标轴json
     * @return {@link ArchivesGraph.Node}
     */
    private ArchivesGraph.Node buildNodeVo(String archivesVid, NebulaGraph.NebulaNode nebulaNode, JSONObject coordinateAxisJson, boolean hideArchivesInfo) {
        ArchivesGraph.Node nodeVo = new ArchivesGraph.Node();
        if (nebulaNode == null) {
            return nodeVo;
        }
        String vid = (String) nebulaNode.getVid();
        String tags = nebulaNode.getTagCode();

        Map<String, Object> properties = nebulaNode.getProperties();

        nodeVo.setId(vid);

        //坐标
        if (coordinateAxisJson != null) {
            JSONObject jsonObject = coordinateAxisJson.getJSONObject(vid);
            if (jsonObject != null) {
                nodeVo.setX(jsonObject.getDouble("x"));
                nodeVo.setY(jsonObject.getDouble("y"));
            }
        }

        ArchivesGraph.Node.LabelCfg labelCfg = new ArchivesGraph.Node.LabelCfg();
        labelCfg.setNodesType(getNodesType(archivesVid, vid, tags));

        if (GraphTagEnum.PERSON_ARCHIVES.tagName.equals(tags)) {
            PersonArchivesTag personArchivesTag = JSONObject.parseObject(JSONObject.toJSONString(properties), PersonArchivesTag.class);
            ArchivesVO archivesVO = new ArchivesVO(personArchivesTag, hideArchivesInfo);
            properties = JSONObject.parseObject(JSONObject.toJSONString(archivesVO), new TypeReference<HashMap<String, Object>>() {
            });
        }
        labelCfg.setProperties(properties);
        tagCodeMapName = getTagCodeMapName();
        labelCfg.setTagCode(new ArchivesGraph.Node.Value(tags, tagCodeMapName.get(tags)));
        nodeVo.setLabelCfg(labelCfg);
        return nodeVo;
    }

    /**
     * 获取节点类型
     *
     * @param archivesVid 档案vid
     * @param vid         从视频
     * @param nodeTags    节点标签
     * @return int
     */
    private int getNodesType(String archivesVid, Object vid, String nodeTags) {
        if (Objects.equals(vid, archivesVid)) {
            //档案主体
            return NumeralPool.ONE;
        }
        if (Objects.equals(GraphTagEnum.PERSON_ARCHIVES.tagName, nodeTags)) {
            //档案副主体
            return NumeralPool.THREE;
        }
        return NumeralPool.TWO;
    }

    /**
     * 构建edge vo
     *
     * @param edge       星云节点
     * @param fromVid    从视频
     * @param toVid      来视频
     * @param startArrow 开始箭头
     * @param endArrow   结束箭头
     * @return {@link ArchivesGraph.Edge}
     */
    private ArchivesGraph.Edge buildEdgeVo(NebulaGraph.NebulaNode edge, String fromVid, String toVid, boolean startArrow, boolean endArrow) {
        ArchivesGraph.Edge edgeVo = new ArchivesGraph.Edge();
        edgeVo.setSource(fromVid);
        edgeVo.setTarget(toVid);

        ArchivesGraph.Edge.Style style = new ArchivesGraph.Edge.Style();
        EdgeBase edgeBase = JSONObject.toJavaObject(JSONObject.parseObject(JSONObject.toJSONString(edge.getProperties())), EdgeBase.class);
        Integer undirected = edgeBase.getUndirected();
        //无向
        if (undirected == 0) {
            startArrow = endArrow = false;
        }
        style.setStartArrow(startArrow);
        style.setEndArrow(endArrow);
        edgeVo.setStyle(style);

        ArchivesGraph.Edge.LabelCfg labelCfg = new ArchivesGraph.Edge.LabelCfg();

        String edgeCode = edge.getVid().toString();
        edgeCodeMapName = getEdgeCodeMapName();
        String edgeName = edgeCodeMapName.get(edgeCode);
        edgeCodeMapType = getEdgeCodeMapType();
        String type = edgeCodeMapType.get(edgeCode);
        //如果是空白边
        if (GraphEdgeEnum.BLANK.edgeName.equals(edgeCode)) {
            edgeCode = null;
            BlankEdge blankEdge = JSONObject.toJavaObject(JSONObject.parseObject(JSONObject.toJSONString(edge.getProperties())), BlankEdge.class);
            type = blankEdge.getExtend();
        }
        labelCfg.setEdgeCode(new ArchivesGraph.Edge.Value(edgeCode, edgeName));
        labelCfg.setType(type);
        edgeVo.setLabelCfg(labelCfg);
        return edgeVo;
    }


}
